﻿using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using Microsoft.Win32;
using Simp.CodeModel;
using Simp.CodeModel.Exceptions;
using Simp.GraphModel;
using Simp.Parsing;
using TypeBuilderFx.Core;
using TypeBuilderFx.Core.Emitters;
using Simp.CodeModel.CoreFunctions;

namespace Simp.Interpreter
{
	/// <summary>Main Window of the application.</summary>
	public partial class MainWindow : Window
	{
		private static readonly IFactory<IContentItem, string, string[]> ContentItemFactory =
			(IFactory<IContentItem, string, string[]>)ImmutablePropertyHolderEmitter.GetFactory<IContentItem>();

		private readonly RecentFiles _recentFiles = RecentFiles.GetRecentFiles();
		private readonly FileSystemWatcher _fileSystemWatcher = new FileSystemWatcher();
		private readonly IParser _parser = new OsloParser();
		private readonly ObservableCollection<IContentItem> _contentItemList = new ObservableCollection<IContentItem>();
		private EvaluationContext _evaluationContext;
		private DispatcherTimer _fileUpdatedTimer;
		private bool _isUpdatingFile = false;

		/// <summary>Constructor.</summary>
		public MainWindow()
		{
			InitializeComponent();

			_fileUpdatedTimer =
				new DispatcherTimer(TimeSpan.FromSeconds(.5), DispatcherPriority.SystemIdle, OnCurrentFileUpdated, Dispatcher);
			_fileUpdatedTimer.Stop();
			ResetEvaluationContext();
			textOutputControl.ContentItemList = _contentItemList;
			RefreshRecentFiles();
		}

		private void RefreshRecentFiles()
		{
			recentFilesMenu.Items.Clear();

			foreach (string filePath in _recentFiles.RecentFilePaths)
			{
				MenuItem item = new MenuItem { Header = Path.GetFileName(filePath) };

				//	Redefine a variable, in order to pass it to an anonymous delegate
				string currentFilePath = filePath;

				item.Click += delegate
				{
					OnLoadFile(currentFilePath);
				};
				recentFilesMenu.Items.Add(item);
			}
		}

		private void inputControl_NewFunction(object sender, RoutedEventArgs e)
		{
			string content = inputControl.LastFunctionContent;

			OnLoadContent(content);
		}

		private void Window_Loaded(object sender, RoutedEventArgs e)
		{
			inputControl.Focus();
		}

		private void TryShowCanvas(IFunction function)
		{
			Canvas canvas = ElementGroupFunction.TryGetCanvas(function);

			if (canvas != null)
			{
				graphOutputUserControl.ModelledCanvas = canvas;
				graphOutputUserControl.Visibility = Visibility.Visible;
				textOutputControl.Visibility = Visibility.Collapsed;
			}
			else
			{
				graphOutputUserControl.Visibility = Visibility.Collapsed;
				textOutputControl.Visibility = Visibility.Visible;
				graphOutputUserControl.ModelledCanvas = null;
			}
		}

		private void OnNewCommand(object sender, ExecutedRoutedEventArgs e)
		{
			var result = MessageBox.Show(
				"Are you sure you want to clear the evaluation context?",
				"New Evaluation Context",
				MessageBoxButton.YesNo,
				MessageBoxImage.Asterisk,
				MessageBoxResult.No);

			if (result == MessageBoxResult.Yes)
			{
				ResetEvaluationContext();
			}
		}

		private void OnOpenCommand(object sender, ExecutedRoutedEventArgs e)
		{
			OpenFileDialog dialog = new OpenFileDialog();

			dialog.InitialDirectory = Directory.GetCurrentDirectory();
			dialog.Filter = "Simp Files (*.simp) | *.simp | Text Files (*.txt) | *.txt";

			if (dialog.ShowDialog(this) == true)
			{
				OnLoadFile(dialog.FileName);
			}
		}

		private void OnLoadFile(string filePath)
		{
			try
			{
				string content = File.ReadAllText(filePath);

				_recentFiles.AddFile(filePath);
				_recentFiles.Save();
				RefreshRecentFiles();
				ResetEvaluationContext();
				_fileSystemWatcher.Path = Path.GetDirectoryName(filePath);
				_fileSystemWatcher.Filter = Path.GetFileName(filePath);
				_fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
				_fileSystemWatcher.EnableRaisingEvents = true;
				_fileSystemWatcher.Changed += new FileSystemEventHandler(_fileSystemWatcher_Changed);
				OnLoadContent(content);
			}
			catch (Exception ex)
			{
				errorOutputControl.SimpException = new SimpRuntimeException(
					string.Format("Impossible to load file '{0}'", Path.GetFileName(filePath)), ex);
			}
		}

		private void OnLoadContent(string content)
		{
			try
			{
				IFunction[] functionList = _parser.Parse(content);
				IFunction[] evaluatedFunctionList = _evaluationContext.Integrate(functionList);
				string[] functionContentList = Array.ConvertAll(evaluatedFunctionList, delegate(IFunction function)
				{
					return FunctionHelper.Serialize(function);
				});
				IContentItem item = ContentItemFactory.Build(content, functionContentList);

				_contentItemList.Add(item);

				errorOutputControl.SimpException = null;

				IFunction lastFunction = evaluatedFunctionList.LastOrDefault();

				if (lastFunction != null)
				{
					TryShowCanvas(lastFunction);
				}
			}
			catch (SimpRuntimeException ex)
			{
				errorOutputControl.SimpException = ex;
			}
			catch (Exception ex)
			{
				errorOutputControl.SimpException = new SimpRuntimeException("Unexpected exception", ex);
			}
		}

		private void ResetEvaluationContext()
		{
			_fileSystemWatcher.EnableRaisingEvents = false;
			_evaluationContext = EvaluationContext.WithPackages(CorePackage.Package, GraphPackage.Package);
			_contentItemList.Clear();
		}

		private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
		{
			if (e.ChangeType == WatcherChangeTypes.Changed && !_isUpdatingFile)
			{	//	Reset timer, to make sure it's not called twice
				_fileUpdatedTimer.Stop();
				_fileUpdatedTimer.Start();
				_isUpdatingFile = true;
			}
		}

		private void OnCurrentFileUpdated(object sender, EventArgs e)
		{
			_fileUpdatedTimer.Stop();

			string path = _recentFiles.RecentFilePaths[0];
			var result = MessageBox.Show(
				string.Format("File '{0}' has been modified.  Do you want to reload it?", path),
				"File Changed",
				MessageBoxButton.YesNo,
				MessageBoxImage.Asterisk,
				MessageBoxResult.No);

			if (result == MessageBoxResult.Yes)
			{
				_isUpdatingFile = false;
				OnLoadFile(path);
			}
		}
	}
}